﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Kivensoft.XlsWriter {
	public class Cell : IComparable<Cell> {
		public enum CellType { STRING, NUMBER, DATE, BOOL, FORMULA, EMPTY, DEFAULT }

		public int RowAddress { get; set; }
		public int ColumnAddress { get; set; }
		public object Value { get; set; }
		public CellType Fieldtype { get; set; }
		public Style CellStyle { get; private set; }

		public Address CellAddress {
			get { return new Address(this.ColumnAddress, this.RowAddress); }
		}


		public Cell() { }

		public Cell(object value, CellType type) {
			Value = value;
			Fieldtype = type;
			ResolveCellType();
		}

		public Cell(object value, CellType type, int column, int row) : this(value, type) {
			ColumnAddress = column;
			RowAddress = row;
			if (type == CellType.DEFAULT) ResolveCellType();
		}

		public void ResolveCellType() {
			if (Value == null) {
				Fieldtype = CellType.EMPTY;
				Value = "";
				return;
			}
			if (Fieldtype == CellType.FORMULA || this.Fieldtype == CellType.EMPTY)
				return; 

			Type t = Value.GetType();
			if (t == typeof(int)) Fieldtype = CellType.NUMBER;
			else if (t == typeof(float)) Fieldtype = CellType.NUMBER;
			else if (t == typeof(double)) Fieldtype = CellType.NUMBER;
			else if (t == typeof(long)) Fieldtype = CellType.NUMBER;
			else if (t == typeof(bool)) Fieldtype = CellType.BOOL;
			else if (t == typeof(DateTime)) Fieldtype = CellType.DATE;
			else Fieldtype = CellType.STRING; // Default
		}

		public string GetCellAddress() {
			return Cell.ResolveCellAddress(ColumnAddress, RowAddress);
		}

		public Style SetStyle(Style style, Workbook workbookReference) {
			if (workbookReference == null)
				throw new UndefinedStyleException("No workbook reference was defined while trying to set a style to a cell");
			if (style == null)
				throw new UndefinedStyleException("No style to assign was defined");
			Style s = workbookReference.AddStyle(style, true);
			CellStyle = s;
			return s;
		}

		public void RemoveStyle(Workbook workbookReference) {
			if (workbookReference == null)
				throw new UndefinedStyleException("No workbook reference was defined while trying to remove a style from a cell");
			string styleName = CellStyle.Name;
			CellStyle = null;
			workbookReference.RemoveStyle(styleName, true);
		}


		public int CompareTo(Cell other) {
			if (RowAddress == other.RowAddress)
				return ColumnAddress.CompareTo(other.ColumnAddress);
			else return RowAddress.CompareTo(other.RowAddress);
		}

		public static List<Cell> ConvertArray<T>(List<T> list) {
			List<Cell> output = new List<Cell>();
			Cell c;
			object o;
			Type t;
			foreach (T item in list) {
				o = (object)item;
				t = item.GetType();

				if (t == typeof(int))
					c = new Cell((int)o, CellType.NUMBER);
				else if (t == typeof(float))
					c = new Cell((float)o, CellType.NUMBER);
				else if (t == typeof(double))
					c = new Cell((double)o, CellType.NUMBER);
				else if (t == typeof(long))
					c = new Cell((long)o, CellType.NUMBER);
				else if (t == typeof(bool))
					c = new Cell((bool)o, CellType.BOOL);
				else if (t == typeof(DateTime))
					c = new Cell((DateTime)o, CellType.DATE);
				else if (t == typeof(string))
					c = new Cell((string)o, CellType.STRING);
				else // Default = unspecified object
					c = new Cell((string)o, CellType.DEFAULT);
				output.Add(c);
			}
			return output;
		}

		public static List<Address> GetCellRange(string range) {
			Range range2 = ResolveCellRange(range);
			return GetCellRange(range2.StartAddress, range2.EndAddress);
		}

		public static List<Address> GetCellRange(string startAddress, string endAddress) {
			Address start = ResolveCellCoordinate(startAddress);
			Address end = ResolveCellCoordinate(endAddress);
			return GetCellRange(start, end);
		}

		public static List<Address> GetCellRange(int startColumn, int startRow, int endColumn, int endRow) {
			Address start = new Address(startColumn, startRow);
			Address end = new Address(endColumn, endRow);
			return GetCellRange(start, end);
		}

		public static List<Address> GetCellRange(Address startAddress, Address endAddress) {
			int startColumn, endColumn, startRow, endRow;
			if (startAddress.ColumnIndex < endAddress.ColumnIndex) {
				startColumn = startAddress.ColumnIndex;
				endColumn = endAddress.ColumnIndex;
			}
			else {
				startColumn = endAddress.ColumnIndex;
				endColumn = startAddress.ColumnIndex;
			}
			if (startAddress.RowIndex < endAddress.RowIndex) {
				startRow = startAddress.RowIndex;
				endRow = endAddress.RowIndex;
			}
			else {
				startRow = endAddress.RowIndex;
				endRow = startAddress.RowIndex;
			}
			List<Address> output = new List<Address>();
			for (int i = startRow; i <= endRow; i++)
				for (int j = startColumn; j <= endColumn; j++)
					output.Add(new Address(j, i));
			return output;
		}

		public static Range ResolveCellRange(string range) {
			if (string.IsNullOrEmpty(range))
				throw new FormatException("The cell range is null or empty and could not be resolved");
			string[] split = range.Split(':');
			if (split.Length != 2)
				throw new FormatException("The cell range (" + range + ") is malformed and could not be resolved");
			return new Range(ResolveCellCoordinate(split[0]), ResolveCellCoordinate(split[1]));
		}

		public static string ResolveCellAddress(int column, int row) {
			if (column >= 16384 || column < 0)
				throw new OutOfRangeException("The column number (" + column.ToString() + ") is out of range. Range is from 0 to 16383 (16384 columns).");
			return ResolveColumnAddress(column) + (row + 1).ToString();
		}

		public static void ResolveCellCoordinate(string address, out int column, out int row) {
			if (string.IsNullOrEmpty(address))
				throw new FormatException("The cell address is null or empty and could not be resolved");
			address = address.ToUpper();
			row = ResolveRow(address);
			column = ResolveColumn(address);
		}

		private static int ResolveRow(string name) {
			if (string.IsNullOrEmpty(name)) throw new Exception("cell index must be not empty!");
			int number = 0;
			int pow = 1;
			for (int i = name.Length - 1; i >= 0; i--) {
				char c = name[i];
				if (c < '0' || c > '9') break;
				number += (c - '0') * pow;
				pow *= 10;
			}
			number--;
			if (number >= 1048576 || number < 0)
				throw new OutOfRangeException("The row number (" + number.ToString() + ") is out of range. Range is from 0 to 1048575 (1048576 rows).");
			return number;
		}

		public static int ResolveColumn(string name) {
			if (string.IsNullOrEmpty(name)) throw new Exception("cell index must be not empty!");
			int number = 0;
			int pow = 1;
			for (int i = name.Length - 1; i >= 0; i--) {
				char c = name[i];
				if (c >= '0' && c <= '9') continue;
				number += (c - 'A' + 1) * pow;
				pow *= 26;
			}
			number--;
			if (number >= 16384 || number < 0)
				throw new OutOfRangeException("The column number (" + number.ToString() + ") is out of range. Range is from 0 to 16383 (16384 columns).");
			return number;
		}

		public static string ResolveColumnAddress(int columnNumber) {
			if (columnNumber >= 16384 || columnNumber < 0) {
				throw new OutOfRangeException("The column number (" + columnNumber.ToString() + ") is out of range. Range is from 0 to 16383 (16384 columns).");
			}
			// A - XFD
			int j = 0, k = 0, l = 0;
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i <= columnNumber; i++) {
				if (j > 25) {
					k++; j = 0;
				}
				if (k > 25) {
					l++; k = 0;
				}
				j++;
			}
			if (l > 0) { sb.Append((char)(l + 64)); }
			if (k > 0) { sb.Append((char)(k + 64)); }
			sb.Append((char)(j + 64));
			return sb.ToString();
		}

		public void SetCellLockedState(bool isLocked, bool isHidden, Workbook workbookReference) {
			Style lockStyle;
			if (CellStyle == null) lockStyle = new Style();
			else lockStyle = CellStyle.Copy();
			lockStyle.CurrentCellXf.Locked = isLocked;
			lockStyle.CurrentCellXf.Hidden = isHidden;
			SetStyle(lockStyle, workbookReference);
		}

		public static Address ResolveCellCoordinate(string address) {
			int row, column;
			ResolveCellCoordinate(address, out column, out row);
			return new Address(column, row);
		}

		public struct Address {
			public int RowIndex;
			public int ColumnIndex;

			public Address(int columnIndex, int rowIndex) {
				ColumnIndex = columnIndex;
				RowIndex = rowIndex;
			}

			public string GetAddress() {
				return ResolveCellAddress(ColumnIndex, RowIndex);
			}

			public override string ToString() {
				return GetAddress();
			}

		}

		public struct Range {
			public Address StartAddress;
			public Address EndAddress;

			public Range(Address start, Address end) {
				StartAddress = start;
				EndAddress = end;
			}
			public override string ToString() {
				return StartAddress.ToString() + ":" + EndAddress.ToString();
			}
		}

	}
}
